#include "StdEddy.h"
#include "Graphics/DDSTextureLoader.h"
#include "Graphics/Texture.h"
#include "Common/FileStream.h"

//------------------------------------------------------------------------------
namespace Eddy
{
#pragma pack(push, 1)
enum
{
    // The surface has alpha channel information in the pixel format.
    DDSPF_ALPHAPIXELS = 0x00000001,
    // The pixel format contains alpha only information
    DDSPF_ALPHA = 0x00000002,
    // The FourCC code is valid.
    DDSPF_FOURCC = 0x00000004,
    // The RGB data in the pixel format structure is valid.
    DDSPF_RGB = 0x00000040,
    // Luminance data in the pixel format is valid.
    // Use this flag for luminance-only or luminance+alpha surfaces,
    // the bit depth is then ddpf.dwLuminanceBitCount.
    DDSPF_LUMINANCE = 0x00020000,
};
struct DDSPIXELFORMAT
{
    uint	size;				// size of structure
    uint	flags;				// pixel format flags
    uint	four_cc;			// (FOURCC code)
    uint	rgb_bit_count;		// how many bits per pixel
    uint	r_bit_mask;			// mask for red bit
    uint	g_bit_mask;			// mask for green bits
    uint	b_bit_mask;			// mask for blue bits
    uint	rgb_alpha_bit_mask;	// mask for alpha channels
};
struct DDSCAPS2
{
    uint	caps1;			// capabilities of surface wanted
    uint	caps2;
    uint	reserved[2];
};
enum
{
    // This flag is used at CreateSurface time to indicate that this set of
    // surfaces is a cubic environment std::map
    DDSCAPS2_CUBEMAP	= 0x00000200,

    // These flags preform two functions:
    // - At CreateSurface time, they define which of the six cube faces are
    //   required by the application.
    // - After creation, each face in the cubemap will have exactly one of these
    //   bits set.
    DDSCAPS2_CUBEMAP_POSITIVEX	= 0x00000400,
    DDSCAPS2_CUBEMAP_NEGATIVEX	= 0x00000800,
    DDSCAPS2_CUBEMAP_POSITIVEY	= 0x00001000,
    DDSCAPS2_CUBEMAP_NEGATIVEY	= 0x00002000,
    DDSCAPS2_CUBEMAP_POSITIVEZ	= 0x00004000,
    DDSCAPS2_CUBEMAP_NEGATIVEZ	= 0x00008000,

    // Indicates that the surface is a volume.
    // Can be combined with DDSCAPS_MIPMAP to indicate a multi-level volume
    DDSCAPS2_VOLUME		= 0x00200000,
};
enum
{
    DDSD_CAPS			= 0x00000001,	// default, dds_caps field is valid.
    DDSD_HEIGHT			= 0x00000002,	// height field is valid.
    DDSD_WIDTH			= 0x00000004,	// width field is valid.
    DDSD_PITCH			= 0x00000008,	// pitch is valid.
    DDSD_PIXELFORMAT	= 0x00001000,	// pixel_format is valid.
    DDSD_MIPMAPCOUNT	= 0x00020000,	// mip_map_count is valid.
    DDSD_LINEARSIZE		= 0x00080000,	// linear_size is valid
    DDSD_DEPTH			= 0x00800000,	// depth is valid
};
struct DDSSURFACEDESC2
{
    uint	size;					// size of the DDSURFACEDESC structure
    uint	flags;					// determines what fields are valid
    uint	height;					// height of surface to be created
    uint	width;					// width of input surface
    union
    {
        int		pitch;				// distance to start of next line (return value only)
        uint	linear_size;		// Formless late-allocated optimized surface size
    };
    uint		depth;				// the depth if this is a volume texture
    uint		mip_map_count;		// number of mip-std::map levels requestde
    uint		reserved1[11];		// reserved
    DDSPIXELFORMAT	pixel_format;		// pixel format description of the surface
    DDSCAPS2		dds_caps;			// direct draw surface capabilities
    uint		reserved2;
};
#pragma pack(pop)
const uint DDSFourCC = ' SDD';	///< "DDS "

//------------------------------------------------------------------------------
Texture::Format
ToTexFormat(const DDSPIXELFORMAT& ddpf)
{
    if (ddpf.flags & DDSPF_FOURCC)	// DXTn
    {
        if ('1TXD' == ddpf.four_cc)
            return Texture::DXT1;
    }
    if (ddpf.flags & DDSPF_RGB)	//rgb codes
    {
        if (32 == ddpf.rgb_bit_count)
        {
            assert(0 != (ddpf.flags & DDSPF_ALPHAPIXELS));
            if ((0x000000FF == ddpf.b_bit_mask) &&
                    (0x0000FF00 == ddpf.g_bit_mask) &&
                    (0x00FF0000 == ddpf.r_bit_mask))
                return Texture::ARGB;
        }
    }
    else if (ddpf.flags & DDSPF_LUMINANCE)
    {
        if (8 == ddpf.rgb_bit_count)
        {
            assert(0 == (ddpf.flags & DDSPF_ALPHAPIXELS));
            return Texture::L8;
        }
        else if (16 == ddpf.rgb_bit_count)
        {
            assert(0 != (ddpf.flags & DDSPF_ALPHAPIXELS));
            return Texture::L8A8;
        }
    }
    else if (ddpf.flags & DDSPF_ALPHA)
    {
        if (8 == ddpf.rgb_bit_count)
            return Texture::A8;
    }

    return Texture::Unknown;
}
//------------------------------------------------------------------------------
uint
GetNumBits(Texture::Format format)
{
    switch (format)
    {
    case Texture::ARGB:
        return 32;
    case Texture::DXT1:
        return 4;
    case Texture::L8A8:
        return 16;
    case Texture::L8:
    case Texture::A8:
        return 8;
    default:
        assert(false);
        return -1;
    }
}

//------------------------------------------------------------------------------
GLenum ToGLTexType(Texture::Type type)
{
    switch (type)
    {
    case Texture::Texture2D:
        return GL_TEXTURE_2D;
    case Texture::TextureCube:
        return GL_TEXTURE_CUBE_MAP;
    }
    assert(false);
    return 0;
}
//------------------------------------------------------------------------------
GLenum
ToGLTexFormat(Texture::Format format)
{
    switch (format)
    {
    case Texture::A8:
        return GL_ALPHA;
    case Texture::DXT1:
        return GL_RGBA_S3TC_OES;
    case Texture::ARGB:
        return GL_BGRA;
    case Texture::L8:
        return GL_LUMINANCE;
    case Texture::L8A8:
        return GL_LUMINANCE_ALPHA;
    }
    assert(false);
    return 0;
}

//------------------------------------------------------------------------------
bool
DDSTextureLoader::OnLoad()
{
    assert(this->resource.isvalid());
    assert(!this->resource->IsLoaded());
    const TexturePtr& texture = this->resource.cast<Texture>();

    FileStream file;
    if (!file.Open(texture->GetName(), FileStream::ReadAccess))
        return false;
    byte* data = (byte*)file.Map();
    uint fourcc = *((uint*)data);
    assert(DDSFourCC == fourcc);

    DDSSURFACEDESC2* desc = (DDSSURFACEDESC2*)(data + sizeof(uint));
    assert(0 != (desc->flags & DDSD_CAPS));
    assert(0 != (desc->flags & DDSD_PIXELFORMAT));
    assert(0 != (desc->flags & DDSD_WIDTH));
    assert(0 != (desc->flags & DDSD_HEIGHT));
    assert(0 == (desc->dds_caps.caps2 & DDSCAPS2_VOLUME));// not supported now

    if (0 == (desc->flags & DDSD_MIPMAPCOUNT))
        desc->mip_map_count = 1;

    // texture attributes
    if (0 != (desc->dds_caps.caps2 & DDSCAPS2_CUBEMAP))
        texture->type = Texture::TextureCube;
    else
        texture->type = Texture::Texture2D;
    texture->format = ToTexFormat(desc->pixel_format);
    texture->width = desc->width;
    texture->height = desc->height;
    texture->numMipLevels = desc->mip_map_count;

    // load pixel data
    GLenum gltarget = ToGLTexType(texture->GetType());
    glBindTexture(gltarget, texture->texture);
    glTexParameterf(gltarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(gltarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameterf(gltarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(gltarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    GLenum glformat = ToGLTexFormat(texture->GetFormat());
    byte* pixelData = (byte*)(data + sizeof(uint) + sizeof(DDSSURFACEDESC2));
    uint bpp = GetNumBits(texture->GetFormat());
    if (Texture::Texture2D == texture->GetType())
    {
        GLsizei width = texture->GetWidth();
        GLsizei height = texture->GetHeight();
        for (IndexT level = 0; level < texture->GetNumMipLevels(); ++level)
        {
            SizeT rowBytes = (width * bpp + 7) / 8; // round up to nearest byte
            SizeT numRows = height;
            SizeT numBytes = rowBytes * numRows;
            if (desc->pixel_format.flags & DDSPF_FOURCC)
            {
                glCompressedTexImage2D(gltarget, level, glformat,
                                       width, height, 0, numBytes, pixelData);
            }
            else
            {
                glTexImage2D(gltarget, level, glformat, width, height, 0,
                             glformat, GL_UNSIGNED_BYTE, pixelData);
            }

            width >>= 1;
            height >>= 1;
            if (0 == width)
                width = 1;
            if (0 == height)
                height = 1;
            pixelData += numBytes;
        }
    }
    else
    {
        // cube std::map
        for (GLenum target = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
                target < GL_MAX_CUBE_MAP_TEXTURE_SIZE; ++target)
        {
            GLsizei width = texture->GetWidth();
            GLsizei height = texture->GetHeight();
            for (IndexT level = 0; level < texture->GetNumMipLevels(); ++level)
            {
                SizeT rowBytes = (width * bpp + 7) / 8; // round up to nearest byte
                SizeT numRows = height;
                SizeT numBytes = rowBytes * numRows;
                if (desc->pixel_format.flags & DDSPF_FOURCC)
                {
                    glCompressedTexImage2D(gltarget, level, glformat,
                                           width, height, 0, numBytes, pixelData);
                }
                else
                {
                    glTexImage2D(gltarget, level, glformat, width, height, 0,
                                 glformat, GL_UNSIGNED_BYTE, pixelData);
                }

                width >>= 1;
                height >>= 1;
                if (0 == width)
                    width = 1;
                if (0 == height)
                    height = 1;
                pixelData += numBytes;
            }
        }
    }

    this->resource = NULL;
    return true;
}

}// Eddy